/**
 * 手撕Promise
 * 首先，promise是一个类,传入一个函数作为参数，直接调用
 * promise 有三个状态， pending， fulfilled， rejected
 * 在 resolve 和 reject调用后状态修改，且状态修改后不能再修改
 * 将 resolve 和 reject 中的参数记录下来，作为 then 方法成功和失败回调的参数
 * 如果 promise 中执行出错，要捕获错误，可以使用try catch来捕获
 * 需要捕获错误的地方包括promise传入的函数执行器，和 then 方法的回调
 */
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECRED = 'rejected'
class MyPromise {
  constructor(fn) {
    try {
      // promise 传入一个函数，直接调用，函数的参数为 resolve 和 reject
      fn(this.resolve, this.reject)
    } catch (err) {
      this.reject(err)
    }
  }
  // 定义初始状态
  status = PENDING
  // then 方法成功回调的参数
  value = undefined
  // then 方法失败回调的参数
  error = undefined
  // 初始化存储 then 回调的值
  sCallback = []
  fCallback = []
  resolve = (value) => {
    // 如果状态不是 pending ，不做修改
    if (this.status !== PENDING) return
    // resolve 后将状态修改为成功
    this.status = FULFILLED
    // 将结果记录
    this.value = value
    // 如果有储存的成功回调，则调用，数组需要循环调用
    // this.sCallback && this.sCallback(value)
    while (this.sCallback.length) this.sCallback.shift()()
  }
  reject = (error) => {
    // 如果状态不是 pending ，不做修改
    if (this.status !== PENDING) return
    // reject 后将状态修改为失败
    this.status = REJECRED
    // 将结果记录
    this.error = error
    // 如果有储存的失败回调，则调用，数组需要循环调用
    // this.fCallback && this.fCallback(error)
    while (this.fCallback.length) this.fCallback.shift()()
  }
  /**
   * then 方法参数为成功回调和失败回调
   * 根据状态判断执行哪个回调
   * 如果是异步调用，执行 then 方法时状态还是 pending，则要将两个回调储存起来
   * 储存的方法在 resolve 和 reject 的方法里对应的调用
   * 同一个promise可能会有多个 then 调用，也就会有多组成功和失败的回调，将异步时回调储存为数组
   * then 方法可以链式调用，所以它返回的是一个promise对象，将回调中返回的值作为下一个then方法的参数
   * then 方法返回的promise对象不能是自身,将 newPromise 与 返回值进行判断
   * 在pending状态也要判断不能返回自身
   * then 方法可以不传递参数，不传递参数时，下一个then可以拿到这个then应该拿到的结果
   * 所以 then 不传递参数时，相当于把结果传递到下一个then
   */
  then(sCallback = value => value, fCallback = error => { throw error }) {
    let newPromise = new MyPromise((resolve, reject) => {
      // 这里是同步执行，所以可以将要执行的操作放在这里
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            // 调用后获取返回的值
            const x = sCallback(this.value)
            // 判断返回的值如果是 promise 对象，根据promise的结果进行resolve和reject
            // 如果是普通值，直接resolve
            // 这个操作在失败是也会调用，所以包装成一个方法
            // then 方法不能返回自己，所以将 newPromise 传进去判断
            // 但是这里其实拿不到newPromise，可以将这段代码放入 setTimeout 中
            // 放入setTimeout 中并不是为了延时，只是为了等 newPromise 创建好了可以引用，所以时间设为0
            thenValue(newPromise, x, resolve, reject)
          } catch (err) {
            reject(err)
          }
        }, 0)
      } else if (this.status === REJECRED) {
        setTimeout(() => {
          try {
            const x = fCallback(this.error)
            thenValue(newPromise, x, resolve, reject)
          } catch (err) {
            reject(err)
          }
        }, 0)
      } else {
        // 调用 then 方法时，promise的异步还没执行完，状态还是pending，把两个回调储存
        // 判断不能返回自身
        this.sCallback.push(() => {
          setTimeout(() => {
            try {
              const x = sCallback(this.value)
              thenValue(newPromise, x, resolve, reject)
            } catch (err) {
              reject(err)
            }
          }, 0)
        })
        this.fCallback.push(() => {
          setTimeout(() => {
            try {
              const x = fCallback(this.error)
              thenValue(newPromise, x, resolve, reject)
            } catch (err) {
              reject(err)
            }
          }, 0)
        })
      }
    })
    return newPromise
  }
  /**
   * 实现finally方法, finally 方法不管promise成功失败都会执行回调
   * finally 会将promise的结果往下传
   * 可以利用 then 方法来实现
   * finally 方法返回一个新的promise对象,由于then方法就是返回一个promise对象，所以直接返回
   * 如果finally返回一个promise对象，要等promise对象有了结果，才会执行下方的 then
   */
  finally(callback) {
    return this.then(value => {
      return MyPromise.resolve(callback()).then(() => value)
    }, err => {
      return MyPromise.resolve(callback()).then(() => { throw err })
    })
  }
  /**
   * 实现 catch,catch方法只有一个回调，就是失败回调,返回一个promise
   */
  catch(callback) {
    return this.then(undefined, callback)
  }
  /**
   * 实现一个all方法, all 方法传入一个数组，数组中会有异步调用，返回一个新的promise对象
   * 数组中所有异步都成功，将结果以数组形式返回，否则一个出错就出错
   */
  static all(args) {
    let results = []
    let index = 0
    return new MyPromise((resolve, reject) => {
      function addData(key, value) {
        results[key] = value
        // index代表给results中添加了几个值，如果index和args长度相等，说明全部成功
        // 不能用results长度来判断，因为results赋值不是通过 push 方法，而是针对 key 来赋值的
        index++
        if (index == args.length) {
          resolve(results)
        }
      }
      for (let i = 0; i < args.length; i++) {
        // 判断是promise对象还是普通值，普通值直接加入results数组
        if (args[i] instanceof MyPromise) {
          // promise 对象
          args[i].then(value => {
            addData(i, value)
          }, reject)
        } else {
          // 普通值
          addData(i, args[i])
        }
      }
    })
  }
  /**
   * 实现一个Promise.resolve方法
   * Promise.resolve方法后面要接 then 方法
   * 参数如果是个promise对象，就按照这个promise执行，返回它
   * 参数如果是个普通值，创建一个新的promise对象
   */
  static resolve(value) {
    if (value instanceof MyPromise) return value
    return new MyPromise(resolve => { resolve(value) })
  }
  /**
   * Promise.reject 方法，返回一个新的Promise，状态为reject
   * 参数原封不动的作为reject的理由
   */
  static reject(reason) {
    return new Promise((resolve, reject) => { reject(reason) })
  }
}
function thenValue(newPromise, x, resolve, reject) {
  if (newPromise === x) return reject(new TypeError('then方法不能返回自己'))
  if (x instanceof MyPromise) {
    // 如果是promise对象
    x.then(resolve, reject)
  } else {
    // 如果是普通值
    resolve(x)
  }
}

let p = new MyPromise((resolve, reject) => {
  resolve('chenggong')
  // reject('shibai')
  // setTimeout(() => {
  //   resolve('异步 成功')
  //   // reject('异步 失败')
  // }, 2000)
})

let p1 = new MyPromise((resolve, reject) => {
  // resolve('p1')
  // reject('shibai')
  setTimeout(() => {
    resolve('异步 成功 ..')
    // reject('异步 失败')
  }, 2000)
})
p.finally(() => {
  console.log('finally')
  return p1
}).then(value => {
  console.log(value, 'then')
}, err => {
  console.log(err, 'err')
})

// MyPromise.reject(p2).then(value => {
//   console.log(value, 'then')
// }, err => {
//   console.log(err, 'err')
// })



// MyPromise.all([p, p1, 3]).then(value => {
//   console.log(value, 'all')
// }, err => {
//   console.log(err, 'all-err')
// })

// let p2 = p.then(value => {
//   console.log(value, 'p')
//   return p1
// }, err => {
//   console.log(err, 'p')
// })
// p2.then(value => {
//   console.log(value, 'then..')
// }, err => {
//   console.log(err, 'catch..')
// })
// .then(value => {
//   console.log(value, 'then...')
// }, err => {
//   console.log(err, 'catch...')
// })

// p.then(value =>{
//   console.log(value, 'p ..')
// },err => {
//   console.log(err,'p ..')
// })

// p.then(value =>{
//   console.log(value, 'p ...')
// },err => {
//   console.log(err,'p ...')
// })